package javango.forms.fields;

import static javango.util.Humanize.humanize;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import javango.forms.Form;
import javango.forms.widgets.Util;
import javango.forms.widgets.Widget;

public class BoundField {

	private Field<?> field;
	private Form form;
	private String name;
	private String htmlName;
	private String label;
	private String helpText;
	
	public BoundField(Field<?> field, Form form, String name) {
		super();
		this.field = field;
		this.form = form;
		this.name = name;		
		this.label = StringUtils.isBlank(field.getLabel()) ? humanize(name) : field.getLabel();
		this.htmlName = form.addPrefix(name);
		this.helpText = StringUtils.isBlank(field.getHelpText()) ? "" : field.getHelpText();		
	}

	public String getHtmlName() {
		return htmlName;
	}

	public String getName() {
		return name;
	}

	public Boolean hasErrors() {
		return form.getErrors().containsKey(field.getName());
	}

	public List<String> getErrors() {
		// TODO getErrors should really return a list or something,  this is returning a list
		// so that when that day happens this is already almost ready..
		List<String> list = new ArrayList<String>();
		String error = form.getErrors().get(field.getName());
		if (error != null)
			list.add(error);
		return list;
	}
	public Widget getWidget() {
		return field.getWidget();
	}
	public Field getField() {
		return field;
	}

	public String toString() {
		return asWidget(null, null);
	}

	public String withAttrs(String stringAttrs ) {
		Map<String, Object> attrs = new LinkedHashMap<String, Object>();

		for (String attr : stringAttrs.split(", ")) {
			String[] split = attr.split("=");
			if (split.length == 2) {
				attrs.put(split[0], split[1]);
			} else if (split.length==1) {
				attrs.put(split[0], null);
			} else  {
				// TODO what to do on a bad formatted string...??
			}
		}

		if (attrs.isEmpty()) return asWidget(null, null);

		return asWidget(null, attrs);
	}

	/**
	 * Returns the value of this bound field,  this method currenly can return just about anything,
	 * from a String, String[] or any object passed in the Field's or Form's initial values.
	 *
	 * This does make it a bit of a pain to use
	 *
	 * @return
	 */
	public Object getValue() {
		if (!form.isBound()) {			
			Map<String, Object> initial = form.getInitial();
			return field.cleanInitialValue(initial.get(name));			
		} else {
			return  field.getWidget().valueFromMap(form.getData(), form.addPrefix(name));
		}

	}

	public String asWidget(Widget widget, Map<String, Object> attrs) {
		if (widget == null) {
			if (field.isHidden()) {
				widget = field.getHiddenWidget(); 
			} else {
				widget = field.getWidget();
			}
		}

		if (attrs == null) {
			attrs = new LinkedHashMap<String, Object>();
		}

		if (StringUtils.isNotBlank(form.getId()) && !attrs.containsKey("id") && !widget.getAttrs().containsKey("id")) {
			attrs.put("id", form.getId());
		}
		if (!field.isEditable()) {
			attrs.put("readonly", "readonly");
		}

		Set<String> extraCss = getExtraCssSet();
		if (extraCss != null) {
			String classes = (String)attrs.get("class");
			if (classes == null) {
				attrs.put("class", Util.join(extraCss, " "));
			} else {
				StringBuilder currentClasses = new StringBuilder(classes);			
				currentClasses.append(" ");
				currentClasses.append(Util.join(extraCss, " "));
				attrs.put("class", currentClasses.toString());
			}
		}

		Object dataObject = getValue(); 

		if (form.isReadOnly()) {
			return widget.asText(dataObject);
		}

		return widget.render(htmlName, dataObject, attrs);
	}

	/**
	 * Returns this field's label,  ie the text that should be shown to the user,  commonly to be inside a 'label' html tag.  
	 * {@link getLabelHtml}
	 * @return
	 */
	public String getLabel() {
		return label;
	}

	public void setLabel(String label) {
		this.label = label;
	}

	/**
	 * @deprecated Use getLabel instead
	 * @return
	 */
	public String getVerboseName() {
		return getLabel();
	}

	/**
	 * @deprecated Use setLabel instead
	 * @return
	 */
	public void setVerboseName(String verboseName) {
		setLabel(verboseName);
	}

	public boolean isHidden() {
		return field.isHidden();
	}

	public String getHelpText() {
		return helpText;
	}

	public void setHelpText(String helpText) {
		this.helpText = helpText;
	}

	public Set<String> getExtraCssSet() {
		Set<String> extraClasses = new LinkedHashSet<String>();
		if (hasErrors() && form.getErrorCssClass() != null) {
			extraClasses.add(form.getErrorCssClass());
		}
		if (getField().isRequired() && form.getRequiredCssClass() != null) {
			extraClasses.add(form.getRequiredCssClass());
		}
		if (extraClasses.isEmpty()) {
			return null;
		}
		return extraClasses;

	}
	public String getCssClasses() {
		Set<String> extraClasses = getExtraCssSet();
		if (extraClasses == null) {
			return null;
		}
		return Util.join(extraClasses, " ");
	}

	/**
	 * Returns the label as an HTML string,  ie <label for='htmlName'>label</label>
	 * @return String, HTML for the label tag.
	 */
	public String getLabelHtml() {
		// DDN, maybe move the template into a configurable string (guice). Then pass the template and this object to a more advanced formatter.
		// ie format(<label for='%(htmlName)s'>%(label)s</label>, this);
		if (form.getId() == null) {
			// no id format,  just return the field's name
			return getLabel();
		}
		return String.format("<label for='%s'>%s</label>", 
				String.format(form.getId(), htmlName)
				, getLabel());
	}
}
